﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Input;
using Workbench.Models;
using Workbench.Operations;
using Workbench.Shared;

namespace Workbench.Presentation
{
    public class ProjectTreeViewModel : ViewModelBase
    {
        public ProjectTreeViewModel()
        {
            RootNodes.CollectionChanged -= Nodes_CollectionChanged;
            RootNodes.CollectionChanged += Nodes_CollectionChanged;
            createDirectoryCommand = new _CreateDirectoryCommand(this);
            addExistingFileCommand = new _AddExistingFileCommand(this);
            addExistingDirectoryCommand = new _AddExistingDirectoryCommand(this);
            removeCommand = new _RemoveCommand(this);
        }

        private ProjectItemViewModel selectedNode;
        public ProjectItemViewModel SelectedNode
        {
            get { return selectedNode; }
            set
            {
                selectedNode = value;
                createDirectoryCommand.RaiseCanExecuteChanged();
                removeCommand.RaiseCanExecuteChanged();
                RaisePropertyChanged();
            }
        }

        private ObservableCollection<ProjectItemViewModel> rootNodes = new ObservableCollection<ProjectItemViewModel>();
        public ObservableCollection<ProjectItemViewModel> RootNodes { get { return rootNodes; } }

        public event Action<object, EventArgs> ProjectChanged;
        private void RaiseProjectChanged()
        {
            if (ProjectChanged != null) ProjectChanged(this, EventArgs.Empty);
        }

        #region Event Handlers 

        private void Nodes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (var treeNode in e.NewItems.Cast<ProjectItemViewModel>())
                {
                    var directoryNode = treeNode as ProjectDirectoryViewModel;
                    if (directoryNode != null)
                    {
                        directoryNode.SetPropertyChangedHandlerRecursive(Files_PropertyChanged);
                        directoryNode.Children.CollectionChanged -= Nodes_CollectionChanged;
                        directoryNode.Children.CollectionChanged += Nodes_CollectionChanged;
                    }
                    else
                    {
                        treeNode.PropertyChanged -= Files_PropertyChanged;
                        treeNode.PropertyChanged += Files_PropertyChanged;
                    }
                }
            }
            if (e.OldItems != null)
            {
                foreach (var treeNode in e.OldItems.Cast<ProjectItemViewModel>())
                {
                    treeNode.PropertyChanged -= Files_PropertyChanged;
                    var directoryNode = treeNode as ProjectDirectoryViewModel;
                    if (directoryNode != null)
                    {
                        directoryNode.Children.CollectionChanged -= Nodes_CollectionChanged;
                    }
                }
            }
        }
        private void Files_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsSelected")
                IsSelected_PropertyChanged(sender, e);
            if (e.PropertyName == "ContextIncluded")
                ContextIncluded_PropertyChanged(sender, e);
        }
        private void ContextIncluded_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var treeNode = sender as ProjectFileViewModel;
            if (treeNode == null)
            {
                return;
            }
            UpdateContextInclude(treeNode);
        }
        private void IsSelected_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var treeNode = sender as ProjectItemViewModel;
            if (treeNode == null)
            {
                return;
            }

            if (treeNode.IsSelected)
            {
                SelectedNode = treeNode;
            }
        }

        #endregion

        #region CreateDirectoryCommand

        readonly _CreateDirectoryCommand createDirectoryCommand;
        public ICommand CreateDirectoryCommand
        {
            get { return createDirectoryCommand; }
        }

        private class _CreateDirectoryCommand : ICommand
        {
            readonly ProjectTreeViewModel buildViewModel;

            public _CreateDirectoryCommand(ProjectTreeViewModel buildViewModel)
            {
                this.buildViewModel = buildViewModel;
            }

            public bool CanExecute(object parameter)
            {
                return (buildViewModel.SelectedNode as ProjectDirectoryViewModel) != null;
            }

            public void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public void Execute(object parameter)
            {
                if (!CanExecute(parameter))
                    return;

                var parentDirectoryNode = buildViewModel.SelectedNode as ProjectDirectoryViewModel;

                var newDirectoryName = getNewDirectoryName(parentDirectoryNode);
                buildViewModel.CreateDirectory(newDirectoryName, parentDirectoryNode);
            }

            private string getNewDirectoryName(ProjectDirectoryViewModel parentDirectoryNode)
            {
                return "New Folder";
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion CreateDirectoryCommand

        #region RemoveCommand

        readonly _RemoveCommand removeCommand;
        public ICommand RemoveCommand
        {
            get { return removeCommand; }
        }

        private class _RemoveCommand : ICommand
        {
            readonly ProjectTreeViewModel buildViewModel;

            public _RemoveCommand(ProjectTreeViewModel buildViewModel)
            {
                this.buildViewModel = buildViewModel;
            }

            public bool CanExecute(object parameter)
            {
                return (buildViewModel.SelectedNode as ProjectItemViewModel) != null
                    && buildViewModel.SelectedNode.Parent != null;

            }

            public void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public void Execute(object parameter)
            {
                if (!CanExecute(parameter))
                    return;

                var viewModelToRemove = buildViewModel.SelectedNode as ProjectItemViewModel;
                var parentViewModel = viewModelToRemove.Parent;

                buildViewModel.RemoveTreeNodeViewModel(viewModelToRemove, parentViewModel);
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion CreateDirectoryCommand

        #region AddExistingFileCommand

        readonly _AddExistingFileCommand addExistingFileCommand;
        public ICommand AddExistingFileCommand
        {
            get { return addExistingFileCommand; }
        }

        private class _AddExistingFileCommand : ICommand
        {
            readonly ProjectTreeViewModel buildViewModel;

            public _AddExistingFileCommand(ProjectTreeViewModel buildViewModel)
            {
                this.buildViewModel = buildViewModel;
            }

            public bool CanExecute(object parameter)
            {
                if (parameter != null)
                {
                    string filePath = parameter as string;
                    if (filePath == null)
                        return false;

                    if (!File.Exists(filePath))
                        return false;
                }
                var selectedDirectoryNode = buildViewModel.SelectedNode as ProjectDirectoryViewModel;
                return selectedDirectoryNode != null;
            }

            public void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public void Execute(object parameter)
            {
                if (!CanExecute(parameter))
                {
                    return;
                }

                var parentDirectoryViewModel = buildViewModel.SelectedNode as ProjectDirectoryViewModel;
                var existingFilePath = parameter as string;
                buildViewModel.AddExistingFile(existingFilePath, parentDirectoryViewModel);
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion AddExistingFileCommand

        #region AddExistingDirectoryCommand

        readonly _AddExistingDirectoryCommand addExistingDirectoryCommand;
        public ICommand AddExistingDirectoryCommand
        {
            get { return addExistingDirectoryCommand; }
        }

        private class _AddExistingDirectoryCommand : ICommand
        {
            readonly ProjectTreeViewModel buildViewModel;

            public _AddExistingDirectoryCommand(ProjectTreeViewModel buildViewModel)
            {
                this.buildViewModel = buildViewModel;
            }

            public bool CanExecute(object parameter)
            {
                if (parameter != null)
                {
                    string directoryPath = parameter as string;
                    if (directoryPath == null)
                        return false;

                    if (!Directory.Exists(directoryPath))
                        return false;
                }
                var selectedDirectoryNode = buildViewModel.SelectedNode as ProjectDirectoryViewModel;
                return selectedDirectoryNode != null;
            }

            public void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public void Execute(object parameter)
            {
                if (!CanExecute(parameter))
                {
                    return;
                }

                var parentDirectoryViewModel = buildViewModel.SelectedNode as ProjectDirectoryViewModel;
                var newDirectoryPath = parameter as string;

                buildViewModel.AddExistingDirectory(newDirectoryPath, parentDirectoryViewModel);
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion AddExistingFileCommand

        #region Actions

        public void RemoveTreeNodeViewModel(ProjectItemViewModel viewModelToRemove, ProjectDirectoryViewModel parentViewModel)
        {
            if (viewModelToRemove is ProjectDirectoryViewModel)
                ProjectOperations.RemoveProjectDirectory(viewModelToRemove.RowId);
            else
                ProjectOperations.RemoveProjectFile(viewModelToRemove.RowId);
            if (parentViewModel != null)
            {
                parentViewModel.Children.Remove(viewModelToRemove);
            }
            RaiseProjectChanged();
        }

        public void CreateDirectory(string directoryName, ProjectDirectoryViewModel parentDirectoryViewModel)
        {
            var directory = ProjectOperations.AddDirectory(directoryName, parentDirectoryViewModel.RowId);
            parentDirectoryViewModel.Children.Add(new ProjectDirectoryViewModel(directory));
            RaiseProjectChanged();
        }

        public void AddExistingDirectory(string existingDirectoryPath, ProjectDirectoryViewModel parentDirectoryViewModel)
        {
            var directory = ProjectOperations.AddExistingDirectory(existingDirectoryPath, parentDirectoryViewModel.RowId);
            parentDirectoryViewModel.Children.Add(new ProjectDirectoryViewModel(directory));
            RaiseProjectChanged();
        }

        public void AddExistingFile(string existingFilePath, ProjectDirectoryViewModel parentDirectoryViewModel)
        {
            var file = ProjectOperations.AddExistingFile(existingFilePath, parentDirectoryViewModel.RowId);
            parentDirectoryViewModel.Children.Add(new ProjectFileViewModel(file));
            RaiseProjectChanged();
        }

        public void Unload()
        {
            this.RootNodes.Clear();
            this.SelectedNode = null;
        }

        public void LoadDirectory(ProjectDirectoryModel rootDirectory)
        {
            var rootDirectoryViewModel = new ProjectDirectoryViewModel(rootDirectory);
            RootNodes.Clear();
            RootNodes.Add(rootDirectoryViewModel);
        }
        
        public async void UpdateContextInclude(ProjectFileViewModel file)
        {
            await ProjectOperations.UpdateFileContextIncludeAsync(file.RowId, file.ContextIncluded);
            RaiseProjectChanged();
        }
        
        #endregion 
    }
}
